package download

import (
	"context"
	"gitee.com/injoyai/goutil/chans"
	"gitee.com/injoyai/goutil/conv"
	"gitee.com/injoyai/goutil/media"
	"gitee.com/injoyai/goutil/oss/io"
	"gitee.com/injoyai/goutil/string/bar"
	"log"
	"time"
)

// NewDownloader 已移到https://github.com/injoyai/downloader
func NewDownloader(op *Option) *Downloader {
	return NewDownloaderWithContext(context.Background(), op)
}

func NewDownloaderWithContext(ctx context.Context, op *Option) *Downloader {
	op = op.new()
	ctx, cancel := context.WithCancel(ctx)
	return &Downloader{
		wait:   chans.NewWaitLimit(op.Limit),
		bar:    bar.New().SetColor(op.BarColor).SetStyle(op.BarStyle),
		ctx:    ctx,
		cancel: cancel,
		retry:  op.Retry,
	}
}

type Downloader struct {
	queue    chan media.Item    //普通队列
	priority chan media.Item    //优先队列
	wait     *chans.WaitLimit   //等待,协程控制
	bar      *bar.Bar           //进度条
	ctx      context.Context    //ctx
	cancel   context.CancelFunc //cancel
	retry    uint               //重试次数
	err      []error            //错误
}

func (this *Downloader) addErr(err error) {
	if err != nil {
		this.err = append(this.err, err)
	}
}

func (this *Downloader) Bar() *bar.Bar {
	return this.bar
}

func (this *Downloader) Retry() int {
	return conv.SelectInt(this.retry <= 0, 1, int(this.retry))
}

func (this *Downloader) runTask(t media.Item) (bytes []byte, err error) {
	for i := 0; i < this.Retry(); i++ {
		bytes, err = t.Run()
		if err == nil {
			return
		}
	}
	return
}

func (this *Downloader) Run(list media.Task, writer io.Writer) []error {
	this.bar.SetSize(float64(list.Len()))
	this.queue = make(chan media.Item, list.Len())
	this.priority = make(chan media.Item, list.Len())
	for _, v := range list.List() {
		this.queue <- v
	}
	fn := func(ctx context.Context, c chan media.Item) {
		cache := make([][]byte, list.Len()+1)
		idx := 0
		for {
			select {
			case <-ctx.Done():
				return
			case i := <-c:
				if cache[i.Idx()] == nil {
					this.wait.Add()
					go func(t media.Item) {
						defer func() {
							this.bar.Add(1)
							this.wait.Done()
						}()
						bytes, err := this.runTask(t)
						this.addErr(err)
						if err == nil {
							cache[i.Idx()] = bytes
							for {
								if bs := cache[idx]; bs != nil {
									writer.Write(bs)
									idx++
									continue
								}
								break
							}
						}
					}(i)
				}
			}
		}
	}
	go fn(this.ctx, this.queue)
	go fn(this.ctx, this.priority)
	go this.bar.Wait()
	time.Sleep(time.Second)
	this.wait.Wait()
	log.Println("下载完成...")
	return this.err
}
